/*
 * Copyright 2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.seppiko.chart.utils;

import com.d_project.qrcode.QRCode;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.encoder.ByteMatrix;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import javax.imageio.ImageIO;
import lombok.Getter;
import org.seppiko.chart.exceptions.SeppikoCheckException;
import org.seppiko.chart.exceptions.SeppikoImageFormatException;
import org.seppiko.chart.exceptions.SeppikoImageGeneratorException;
import org.seppiko.chart.exceptions.SeppikoProcessorException;
import org.seppiko.chart.models.BarcodeEntity;
import org.seppiko.chart.models.MessageEntity;
import org.seppiko.chart.utils.logging.LoggingManager;
import org.seppiko.commons.utils.Base64Util;
import org.springframework.http.MediaType;

/**
 * @author Leonard Woo
 */
public class ImageUtil {

  private static final LoggingManager Logger = LoggingManager.getInstance().getLogger(ImageUtil.class.getName());

  @Getter
  private MediaType type;

  public byte[] svgGenerator(BarcodeEntity entity, ByteMatrix matrix) throws SeppikoImageGeneratorException {
    try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
      String content = entity.getNeedText() ? entity.getData() : null;
      ZxingImageUtil.toSVGDocument(matrix, content,
          new OutputStreamWriter(new BufferedOutputStream(out), StandardCharsets.ISO_8859_1.name()),
          entity.getWidth(), entity.getHeight(), entity.getMargin(),
          Integer.decode(entity.getColor()), Integer.decode(entity.getBackgroundColor()) );

      return out.toByteArray();
    } catch (IOException ex) {
      throw new SeppikoImageGeneratorException(ex.getMessage(), ex);
    }
  }

  public byte[] svgGenerator(BarcodeEntity entity, boolean[] matrix)
      throws SeppikoProcessorException {
    try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
      String content = entity.getNeedText() ? entity.getData() : null;
      ZxingImageUtil.toSVGDocument(matrix,
          new OutputStreamWriter(new BufferedOutputStream(out), StandardCharsets.ISO_8859_1.name()),
          content,
          entity.getWidth(), entity.getHeight(), entity.getMargin(),
          Integer.decode(entity.getColor()), Integer.decode(entity.getBackgroundColor()) );

      return out.toByteArray();
    } catch (IOException ex) {
      throw new SeppikoImageGeneratorException(ex.getMessage(), ex);
    }
  }

  public byte[] genImage(BarcodeEntity entity, ByteMatrix matrix)
      throws SeppikoCheckException, SeppikoProcessorException {
    BufferedImage image = ZxingImageUtil.toBufferedImage(matrix,
        entity.getHeight(), entity.getWidth(), entity.getMargin(),
        Integer.decode(entity.getColor()), Integer.decode(entity.getBackgroundColor()) );
    return imageGenerator(entity, image);
  }

  public byte[] genImage(BarcodeEntity entity, BitMatrix matrix)
      throws SeppikoCheckException, SeppikoProcessorException {
    BufferedImage image =  ZxingImageUtil.toBufferedImage(matrix,
        Integer.decode(entity.getColor()), Integer.decode(entity.getBackgroundColor()) );
    return imageGenerator(entity, image);
  }

  public byte[] genImage(BarcodeEntity entity, boolean[] matrix)
      throws SeppikoCheckException, SeppikoProcessorException {
    BufferedImage image =  ZxingImageUtil.toBufferedImage(matrix,
        entity.getHeight(), entity.getWidth(),entity.getMargin(),
        Integer.decode(entity.getColor()), Integer.decode(entity.getBackgroundColor()) );
    return imageGenerator(entity, image);
  }

  public byte[] genDImg(BarcodeEntity entity, QRCode qrCode)
      throws SeppikoCheckException, SeppikoProcessorException {
    try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
      if (entity.getFormat().equalsIgnoreCase("gif")) {
        type = MediaType.IMAGE_GIF;
        DProjectUtil.createGIFImage(qrCode, entity.getSize(), entity.getMargin()).write(out);
        return out.toByteArray();
      } else {
        BufferedImage image = qrCode.createImage(entity.getSize(), entity.getMargin());
        return imageGenerator(entity, image);
      }
    } catch (IOException e) {
      Logger.warn("ImageGenUtil.imageGenerator", e);
      throw new SeppikoProcessorException();
    }
  }

  private byte[] imageGenerator(BarcodeEntity entity, BufferedImage image)
      throws SeppikoCheckException, SeppikoProcessorException {
    try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {

      if (image == null) {
        throw new SeppikoCheckException("Image is null");
      }

      if (entity.getNeedText()) {
        image = ZxingImageUtil.addTextOnBottom(image, entity.getData(), 10);
      }

      if (!ImageIO.write(image, entity.getFormat().toLowerCase(), out)) {
        String errorMsg = "Could not write an image of format " + entity.getFormat().toLowerCase();
        Logger.error(errorMsg);
        throw new SeppikoImageGeneratorException(errorMsg);
      }

      String base64 = Base64Util.encodeToString(out.toByteArray());
      if (entity.getNeedBase64()) {
        return base64.getBytes();
      }
      if (entity.getEnableJson()) {
        type = MediaType.APPLICATION_JSON;
        return JsonUtil.toJson(new MessageEntity(200, base64)).getBytes();
      }

      if (entity.getFormat().equalsIgnoreCase("png")) {
        type = MediaType.IMAGE_PNG;
      } else if (entity.getFormat().equalsIgnoreCase("gif")) {
        type = MediaType.IMAGE_GIF;
      } else if (entity.getFormat().equalsIgnoreCase("jpg") ||
          entity.getFormat().equalsIgnoreCase("jpeg")) {
        type = MediaType.IMAGE_JPEG;
      } else {
        throw new SeppikoImageFormatException("Not implement image");
      }
      return out.toByteArray();
    } catch (IOException e) {
      Logger.warn("ImageGenUtil.imageGenerator", e);
      throw new SeppikoProcessorException();
    }
  }

  private static final int MAX_PIXELS = 1 << 25;
  /**
   * Image Reader
   */
  public static BufferedImage processStream(InputStream is) {
    BufferedImage image;
    try {
      image = ImageIO.read(is);
    } catch (IOException | IllegalArgumentException | ArrayIndexOutOfBoundsException e) {
      Logger.info(e.toString());
      return null;
    }
    if (image == null) {
      return null;
    }

    int height = image.getHeight();
    int width = image.getWidth();
    if (height <= 1 || width <= 1 || height * width > MAX_PIXELS) {
      Logger.info("Dimensions out of bounds: " + width + 'x' + height);
      return null;
    }
    return image;
  }

}
